﻿/**
 * MOTOR 2 - RAPID FLASH DYNAMICS
 * Copyright (c) 2006-2008 Michael Baczynski http://www.polygonal.de
 * 
 * Based on Box2D by Erin Catto, http://www.box2d.org
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 * 
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 * 
 * 1. The origin of this software must not be misrepresented; you must not
 * claim that you wrote the original software. If you use this software
 * in a product, an acknowledgment in the product documentation would be
 * appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 * misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 */
package de.polygonal.motor2.dynamics.contact 
{
	import de.polygonal.motor2.World;
	import de.polygonal.motor2.collision.nbody.IPairCallback;
	import de.polygonal.motor2.collision.shapes.ShapeSkeleton;
	import de.polygonal.motor2.dynamics.RigidBody;
	import de.polygonal.motor2.dynamics.contact.Contact;
	import de.polygonal.motor2.dynamics.contact.ContactNode;
	import de.polygonal.motor2.dynamics.contact.NullContact;	

	public class ContactManager implements IPairCallback
	{
		public var statsContactCount:int;
		public var destroyImmediate:Boolean;
		
		private var _nullContact:NullContact = new NullContact();
		private var _world:World;
		
		public function ContactManager(w:World) 
		{
			this._world = w;
		}
		
		/**
		 * Invoked by the broad-phase when two proxies start to overlap. This
		 * creates a contact object to manage the narrow phase.
		 */
		public function pairAdded(proxyUserData1:*, proxyUserData2:*):Contact
		{
			++statsContactCount;
		
			var s1:ShapeSkeleton = proxyUserData1 as ShapeSkeleton;
			var s2:ShapeSkeleton = proxyUserData2 as ShapeSkeleton;
			
			var b1:RigidBody = s1.body;
			var b2:RigidBody = s2.body;
			
			if (b1 == b2) return _nullContact;

			//collisions between two static objects
//			if ((b1.stateBits & b2.stateBits) & RigidBody.k_bitStatic)
//				return nullContact;

			if (b1.isStatic() && b2.isStatic())
			{
				trace("warning, nullcontact created due to two static bodies");
				return _nullContact;
			}
			
			//ensure that body2 is dynamic (body1 is static or dynamic)
			if (b2.invMass == 0)
			{
				var ts:ShapeSkeleton = s1;
				s1 = s2;
				s2 = ts;
				
				var tb:RigidBody = b1;
				b1 = b2;
				b2 = tb;
			}
			
			//TODO joints.
			//if (b2.isConnected(b1))
			
			/*/////////////////////////////////////////////////////////
			// CREATE CONTACT
			/////////////////////////////////////////////////////////*/
			
			var c:Contact = Contact.Create(s1, s2);
			
			if (c == null)
				return _nullContact;
			else
			{
				//prepend to world's contact list
				c.prev = null;
				c.next = _world.contactList;
				if (_world.contactList) _world.contactList.prev = c;
				
				_world.contactList = c;
				_world.contactCount++;
			}
			return c;
		}
		
		/**
		 * Invoked by the broad-phase when to proxies cease to overlap.
		 * This destroy the contact object.
		 */
		public function pairRemoved(proxyUserData1:*, proxyUserData2:*, pairUserData:*):void
		{
			//TODO cleanup pairRemoved
			--statsContactCount;
			
			if (pairUserData == null)
			{
				return;
			}
			
			var c:Contact = pairUserData as Contact;
			if (c != _nullContact)
			{
				if (destroyImmediate == true)
				{
					destroyContact(c);
					c = null;
				}
				else
					c.stateBits |= Contact.k_bitDestroy;
			}
		}
		
		/**
		 * Destroys all contacts marked for deferred destruction.
		 */
		public function cleanContactList():void
		{
			var c:Contact = _world.contactList, t:Contact;
			while (c)
			{
				t = c;
				c = c.next;
				
				if (t.stateBits & Contact.k_bitDestroy)
					destroyContact(t);
				
				t = null;
			}
		}
		
		/**
		 * The top level collision call for the time step. Here all
		 * narrow phase collisions are processed for the world contact list.
		 */
		public function collide():void
		{
			var c:Contact = _world.contactList;
			while (c)
			{
				var b1:RigidBody = c.shape1.body;
				var b2:RigidBody = c.shape2.body;
				
				//don't disturb two sleeping bodies
				if ((b1.stateBits & b2.stateBits) & RigidBody.k_bitSleep)
				{
					c = c.next;
					continue;
				}
				
				/*/////////////////////////////////////////////////////////
				// CREATE CONTACT MANIFOLD
				/////////////////////////////////////////////////////////*/
				
				var oldCount:int = c.manifoldCount;
				c.evaluate();
				var newCount:int = c.manifoldCount;
				
				/*/////////////////////////////////////////////////////////
				// UPDATE CONTACT GRAPH 
				/////////////////////////////////////////////////////////*/
				
				var n:ContactNode;
				if (oldCount == 0 && newCount > 0)
				{
					//connect to island graph
					//connect to body 1
					n = c.node1;
					n.contact = c;
					n.other = b2;
					
					//prepend to contact node list
					n.prev = null;
					n.next = b1.contactList;
					if (n.next) n.next.prev = n;
					b1.contactList = n;
					
					//connect to body 2
					n = c.node2;
					n.contact = c;
					n.other   = b1;
					
					//prepend to contact node list
					n.prev = null;
					n.next = b2.contactList;
					if (n.next) n.next.prev = n;
					b2.contactList = n;
				}
				else
				if (oldCount > 0 && newCount == 0)
				{
					//disconnect from island graph
					//unlink from body 1
					n = c.node1;
					if (n.next) n.next.prev = n.prev;
					if (n.prev) n.prev.next = n.next;
					if (n == b1.contactList) b1.contactList = n.next;
					n.next = n.prev = null;
					
					//unlink from body 2
					n = c.node2;
					if (n.next) n.next.prev = n.prev;
					if (n.prev) n.prev.next = n.next;
					if (n == b2.contactList) b2.contactList = n.next;
					n.next = n.prev = null;
				}
				
				c = c.next;
			}
		}
		
		private function destroyContact(c:Contact):void
		{
			if (_world.contactCount == 0) return;
			
			//unlink from world's contact list
			if (c.prev) c.prev.next = c.next;
			if (c.next) c.next.prev = c.prev;
			if (c == _world.contactList) _world.contactList = c.next;
			
			//unlink existing contact points from island graph
			if (c.manifoldCount > 0)
			{
				var b1:RigidBody = c.shape1.body;
				b1.stateBits &= ~RigidBody.k_bitSleep;
				b1.sleepTime = 0;
				
				var b2:RigidBody = c.shape2.body;
				b2.stateBits &= ~RigidBody.k_bitSleep;
				b2.sleepTime = 0;
				
				var n:ContactNode;
				
				n = c.node1;
				if (n.next) n.next.prev = n.prev;
				if (n.prev) n.prev.next = n.next;
				if (n == b1.contactList) b1.contactList = n.next;
				n.next = n.prev = null;
				
				n = c.node2;
				if (n.next) n.next.prev = n.prev;
				if (n.prev) n.prev.next = n.next;
				if (n == b2.contactList) b2.contactList = n.next;
				n.next = n.prev = null;
			}
			_world.contactCount--;
		}
	}
}